#!/bin/bash -e
# shellcheck disable=SC2034,SC2317

# Global Defines
BASE_NAME="__BASE_NAME__"
ALL_OSVER="__ALL_OSVER__"
ALL_MAINLINE="__ALL_MAINLINE__"
ALL_CPU="__ALL_CPU__"
ALL_CPU_HUAWEI="__ALL_CPU_HUAWEI__"

G_SPLIT_CHAR="@"

[ -n "${G_PATCHES_LIST}" ] || G_PATCHES_LIST=${BASE_NAME}.list
[ -n "${G_INSTALL_LIST}" ] || G_INSTALL_LIST=${BASE_NAME}-install.list
[ -n "${G_INSTALL_LOG}" ] || G_INSTALL_LOG=${BASE_NAME}-install.log

[ -n "${G_INSTALL_LIST_TEXT}" ] || G_INSTALL_LIST_TEXT=${BASE_NAME}-install.txt
[ -n "${G_INSTALL_LIST_HTML}" ] || G_INSTALL_LIST_HTML=${BASE_NAME}-install.html
[ -n "${G_CHANGELOG_TEXT}" ] || G_CHANGELOG_TEXT=${BASE_NAME}-changelog.txt
[ -n "${G_CHANGELOG_HTML}" ] || G_CHANGELOG_HTML=${BASE_NAME}-changelog.html

# Scripts
G_ACTIONS_D=actions.d
G_ACTIONS_FILE_PRE=pre-actions.sh
G_ACTIONS_FILE_POST=post-actions.sh

# Script folder
EXEC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# COLOR SETTINGS
C_CLR="\\e[0m"
C_H="\\e[1m"

C_R="\\e[31m"
C_G="\\e[32m"
C_B="\\e[36m"
C_Y="\\e[33m"

C_HR="${C_H}${C_R}"
C_HG="${C_H}${C_G}"
C_HB="${C_H}${C_B}"
C_HY="${C_H}${C_Y}"

C_OK="${C_G}OK${C_CLR}"
C_FL="${C_R}FAILED${C_CLR}"
C_WARN="${C_Y}WARNING${C_CLR}"
C_ERROR="${C_R}ERROR${C_CLR}"

### DIALOG SETTINGS
WINDOW_WIDTH=900
WINDOW_HEIGHT=500
WINDOW_ICON=''
export WINDOW_WIDTH WINDOW_HEIGHT WINDOW_ICON

### FUNCTIONS
G_INSTALLER_MODE=""
function HasZenity() {
    which zenity > /dev/null
    return $?
}

function GetInstallerMode() {
    local Mode=$1
    if echo "${Mode}" | grep -ivq "GUI"; then
        Mode="CLI"
    fi
    echo "${Mode}"
}

function SetInstallerMode() {
    local Mode=$1
    G_INSTALLER_MODE="$(GetInstallerMode "${Mode}")"
}

function GetHuaweiCpuType() {
    local CpuType DMIData
    if [ -f /proc/hardware ];then
        if grep -rin "huawei.*9006" /proc/hardware; then
            CpuType=klvv
        elif grep -rinE "[huawei|kirin].*990" /proc/hardware ;then
            DMIData=$(dmidecode |grep "String 4")
            if [[ ${DMIData} =~ .*KVK90.* ]];then
                CpuType=klvu
            else
                CpuType=pguv
            fi
        elif grep -irn "pangu.*900" /proc/hardware;then
            DMIData=$(dmidecode |grep "String 4")
            if [[ ${DMIData} =~ "PWC30" ]];then
                CpuType=pguw
            else
                CpuType=pangum900
            fi
        fi
    else
        DMIData=$(dmidecode -s system-product-name)
        CpuType="${DMIData#* }"
        if [[ "${DMIData}" =~ "PGUV" ]]; then
            CpuType=pguv
        elif [[ "${DMIData}" =~ "KLVU" ]]; then
            CpuType=klvu
        elif [[ "${DMIData}" =~ "KLVV" ]]; then
            CpuType=klvv
        else
            CpuType=unknown
        fi
    fi
    echo "${CpuType}"
}

function GetCPUModel() {
    local CPU_MODEL
    CPU_MODEL=$(LC_ALL=C lscpu | awk -F":" '/Model name/ {print $2}' | sed 's/^[ ]*//g')
    echo "${CPU_MODEL}"
}

function IsHuaweiCPU() {
    GetCPUModel | grep -iq "HUAWEI" > /dev/null
    return $?
}

function GetHuaweiCPUModel() {
    local HUAWEI_CPU_MODEL
    HUAWEI_CPU_MODEL=$(GetCPUModel | awk '{print toupper($3)}')
    echo "${HUAWEI_CPU_MODEL}"
}

function GetOSTyp() {
    if which dpkg > /dev/null && which apt > /dev/null; then
        echo "deb"
    elif which rpm > /dev/null && which yum > /dev/null || which dnf > /dev/null; then
        echo "rpm"
    else
        echo "Unsupported operation system!"
        exit 1
    fi
}

function GetOSArch() {
    local OSArch OSType
    OSType=$(GetOSTyp)

    if IsHuaweiCPU; then
        OSArch="huawei-$(GetHuaweiCPUModel)"
    else
        if [ "${OSType}" = "deb" ]; then
            OSArch="$(dpkg --print-architecture)"
        else
            OSArch="$(rpm --eval '%{_arch}')"
        fi
    fi
    echo "${OSArch}"
}

function GetOSVersion() {
    local OSVer
    if grep -q "^PROJECT_CODENAME=" /etc/os-release || IsHuaweiCPU; then
        OSVer=$(awk -F '=' '/^PROJECT_CODENAME=/ {print $2}' /etc/os-release | sed 's|"||g' | tr '[:upper:]' '[:lower:]')
    else
        OSVer="v10"
    fi
    echo "${OSVer}"
}

function GetOSMainlineVersion() {
    local ReleaseID OSVer Mainline
    OSVer=$(GetOSVersion)
    if [ "${OSVer}" = "v10" ]; then
        Mainline=""
    else
        if grep -q "^KYLIN_RELEASE_ID=" /etc/os-release; then
            Mainline=$(awk -F '=' '/^KYLIN_RELEASE_ID=/ {print $2}' /etc/os-release | sed 's|"||g')
        else
            Mainline="2107"
        fi
    fi
    echo "${Mainline}"
}

function GetOSMilestone() {
    local Milestone
    local KyInfoFile="/etc/.kyinfo"
    if [ -f "${KyInfoFile}" ]; then
        Milestone=$(awk -F '=' '/^milestone=/ {print $2}' ${KyInfoFile} | sed 's|"||g')
    fi
    echo "${Milestone}"
}

function GetOSDistID() {
    local OSDistID
    local KyInfoFile="/etc/.kyinfo"
    if [ -f "${KyInfoFile}" ]; then
        OSDistID=$(awk -F '=' '/^dist_id=/ {print $2}' ${KyInfoFile} | sed 's|"||g')
    fi
    echo "${OSDistID}"
}

function GetOSBuildID() {
    local OSBuildID
    local KylinBuildFile="/etc/kylin-build"
    if [ -f "${KylinBuildFile}" ]; then
        OSBuildID=$(awk '/^buildid:/ {print $2}' ${KylinBuildFile} | sed 's|"||g')
    fi
    echo "${OSBuildID}"
}

function GetOSBuildDate() {
    local OSBuildDate
    local KylinBuildFile="/etc/kylin-build"
    if [ -f "${KylinBuildFile}" ]; then
        OSBuildDate=$(awk '/^Build/ {print $2}' ${KylinBuildFile} | sed 's|"||g')
    fi
    echo "${OSBuildDate}"
}

function GetPatchFolder() {
    local PatchDir OSVer Mainline HuaweiCpu CpuModel
    OSVer=$1
    Mainline=$2

    if [ "${ALL_CPU}" = true ] || ! IsHuaweiCPU; then
        HuaweiCpu=""
    else
        if [ "${ALL_CPU_HUAWEI}" = true ]; then
            CpuModel="all"
        else
            CpuModel=$(GetHuaweiCPUModel)
        fi
        HuaweiCpu=huawei${CpuModel:+-${CpuModel}}
    fi

    if [ "${ALL_OSVER}" = true ]; then
        if [ -z "${HuaweiCpu}" ]; then
            OSVer=""
        else
            OSVer="all"
        fi
        Mainline=""
    else
        if [ -z "${OSVer}" ]; then
            OSVer=$(GetOSVersion)
        fi
        if [ "${ALL_MAINLINE}" = true ]; then
            if [ "${OSVer}" != "v10" ]; then
                Mainline="all"
            else
                Mainline=""
            fi
        else
            if [ -z "${Mainline}" ]; then
                Mainline=$(GetOSMainlineVersion)
            fi
        fi
    fi

    PatchDir="${OSVer:+${OSVer}/}${HuaweiCpu:+${HuaweiCpu}/}${Mainline:+${Mainline}}"

    # Remove the end if var endwith '/'
    if [ "${PatchDir: -1}" = "/" ]; then
        PatchDir=${PatchDir:0:-1}
    fi
    echo "${PatchDir}"
}

function GUI_Caller() {
    local Mode=$1
    local Source=$2
    local Exec=$3

    if [ ! -f "${Source}" ]; then
        # echo "Cannot found source file {${Source}}"
        echo "无法找到源文件: {${Source}}"
        exit 1
    fi

    SetInstallerMode "${Mode}"
    if [ "${G_INSTALLER_MODE}" = "GUI" ]; then
        pkexec sudo -E bash -c "source ${Source}; ${Exec}"
    else
        sudo -E bash -c "source ${Source}; ${Exec}"
    fi
}

function GUI_ShowInfo() {
    local Mode=$1
    local InfoType=$2
    local Title=$3
    local Contents=$4

    local MessageType=ERROR
    local ZenityOptions=(
        --width "${WINDOW_WIDTH}"
        --height "${WINDOW_HEIGHT}"
        --title "\"${Title}\""
    )

    case "${InfoType}" in
        info)
            ZenityOptions+=(--info --text "\"${Contents}\"")
            MessageType=INFO
            ;;
        error)
            ZenityOptions+=(--error --text "\"${Contents}\"")
            MessageType=ERROR
            ;;
        text-info)
            ZenityOptions+=(--text-info --filename "\"${Contents}\"")
            MessageType=INFO
            ;;
        *)
            ;;
    esac

    SetInstallerMode "${Mode}"
    if [ "${G_INSTALLER_MODE}" = "GUI" ] && which zenity >/dev/null 2>&1; then
        eval zenity "${ZenityOptions[*]}" 2>/dev/null || exit $?
    else
        echo "${MessageType}: ${Contents}"
    fi
}

function GUI_ShowProgress() {
    local Mode=$1
    local Title=$2
    local Contents=$3
    local Line Progress Mark

    local ZenityOptions=(
        --width "${WINDOW_WIDTH}"
        --height "${WINDOW_HEIGHT}"
        --title "\"${Title}\""
        --text "\"${Contents}\""
        --progress
        --no-cancel
        --percentage 0
        --auto-close
    )

    SetInstallerMode "${Mode}"
    if [ "${G_INSTALLER_MODE}" = "GUI" ] && which zenity >/dev/null 2>&1; then
        # echo "Progress: ${Contents}"
        echo "进度: ${Contents}"
        eval zenity "${ZenityOptions[*]}" 2>/dev/null || exit $?
    else
        # echo "Progress: ${Contents}"
        echo "进度: ${Contents}"
        Progress=0
        read -r Line
        while [ -n "${Line}" ] && [ "${Progress}" -le 100 ]; do
            if [ "${Progress}" -eq 0 ] || [ "${Line}" -ge "$((Progress))" ]; then
                Progress=$((Progress + 5))
                if [ "${Progress}" -gt 0 ]; then
                    Mark="##${Mark}"
                fi
                printf "[%-40s] %d%%\r" "${Mark}" "${Progress}"
            fi
            read -r Line
        done
        echo
    fi
}

function GUI_ShowHtml() {
    local Mode=$1
    local HtmlFile=$2
    local Title=$3
    local OKButtonLabel=$4

    local ZenityOptions=(
        --width "${WINDOW_WIDTH}"
        --height "${WINDOW_HEIGHT}"
        --title "\"${Title}\""
        --text-info
        --html
        --no-interaction
        --filename "\"${HtmlFile}\""
    )

    if [ -n "${OKButtonLabel}" ]; then
        ZenityOptions+=(--ok-label "${OKButtonLabel}")
    fi

    SetInstallerMode "${Mode}"
    if [ "${G_INSTALLER_MODE}" = "GUI" ] && which zenity >/dev/null 2>&1; then
        eval zenity "${ZenityOptions[*]}" 2>/dev/null || exit $?
    else
        # echo "  HTML FILE: ${HtmlFile}"
        echo "  页面文件: ${HtmlFile}"
    fi
}

function Html_GenHeader() {
    {
        echo "<head>"
        echo "<style type='text/css'>td {background-color:#ffffff;height:25px;line-height:150%;}</style>"
        echo "<style type='text/css'>.tabtop {margin-top: 13px;}</style>"
        echo "<style type='text/css'>.headbg {background:#e9faff !important;}</style>"
        echo "</head>"
        echo "<body>"
    } >> "${G_INSTALL_LIST_HTML}"
}

function Html_GenTail() {
    {
        echo "</table>"
        echo "</body>"
    } >> "${G_INSTALL_LIST_HTML}"
}

function Html_GenTableHeader() {
    local Columns Header
    read -r -a Columns <<< "$@"
    for Column in "${Columns[@]}"; do
        Header="${Header:+${Header}}<th class='headbg'>${Column}</th>"
    done
    {
        echo "<table width='100%' border='0' cellspacing='1' cellpadding='4' class='tabtop'  bgcolor='#cccccc'>"
        echo "<tr>${Header}</tr>"
    } >> "${G_INSTALL_LIST_HTML}"
}

function Html_GenTableBody() {
    local Name Version Depends OSArch OSVer File Summary

    if [ -f "${G_INSTALL_LIST}" ]; then
        ### ${Name} | ${Version} | ${Depends} | ${OSArch} | ${OSVer} | ${PatchFile} | ${Summary}
        while read -r Line; do
            Name=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $1}')
            Version=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $2}')
            Depends=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $3}')
            OSArch=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $4}')
            OSVer=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $5}')
            File=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $6}')
            Summary=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $7}')
            echo "<tr><td>${Name}</td><td>${Version}</td><td>${OSArch}</td><td>${OSVer}</td><td>${Summary}</td></tr>" >> "${G_INSTALL_LIST_HTML}"
        done < "${G_INSTALL_LIST}"
    else
        # echo "Generate html TableBody failed, cannot find install list file: {${G_INSTALL_LIST}}"
        echo "生成页面失败，无法找到安装列表文件: {${G_INSTALL_LIST}}"
    fi
}

function Html_GenerateList() {
    local OSArch=$1
    local OSVer=$2
    local OSMainline=$3

    [ -n "${OSArch}" ] || OSArch="$(GetOSArch)"
    [ -n "${OSVer}" ] || OSVer="$(GetOSVersion)"
    [ -n "${OSMainline}" ] || OSMainline="$(GetOSMainlineVersion)"

    rm -f "${G_INSTALL_LIST_HTML}"
    # echo "Generate Install List Html: ${G_INSTALL_LIST_HTML}"
    echo "生成安装列表页面: ${G_INSTALL_LIST_HTML}"

    if [ "${OSVer}" != "v10" ]; then
        OSVer="${OSVer}${OSMainline:+ ${OSMainline}}"
    fi
    Html_GenHeader
    Html_GenTableHeader "补丁" "版本" "架构" "适用系统" "描述"
    Html_GenTableBody "${OSArch}" "${OSVer}"
    Html_GenTail
}

function Call_Actions() {
    local ActionsType=$1
    local ActionsFile=$2
    local UserID=$3
    local OSVer=$4
    local OSMainline=$5
    local ReturnCode=0

    [ -n "${OSVer}" ] || OSVer="$(GetOSVersion)"
    [ -n "${OSMainline}" ] || OSMainline="$(GetOSMainlineVersion)"

    if [ -f "${ActionsFile}" ]; then
        echo "Call ${ActionsType}-Actions ..."
        echo "Actions File: $(basename "${ActionsFile}"), ARGS: [${UserID}] [${OSVer}] [${OSMainline}]"
        echo "===================================================================================="
        if file "${ActionsFile}" | grep -q "shell script"; then
            bash "${ActionsFile}" "${UserID}" "${OSVer}" "${OSMainline}" 2>&1
            ((ReturnCode = ReturnCode + $?))
        elif file "${ActionsFile}" | grep -q "executable"; then
            "${ActionsFile}" "${UserID}" "${OSVer}" "${OSMainline}" 2>&1
            ((ReturnCode = ReturnCode + $?))
        fi
        echo "===================================================================================="
        echo "Call ${ActionsType}-Actions Done"
    fi
}

function Data_GenPatchInstallList() {
    local WorkDir=$1
    local OSArch=$2
    local OSVer=$3
    local OSMainline=$4
    local ListContents PackageManager
    local PatchSum PatchFile PatchBaseDir PatchSuffix Sha256Sum
    local Name Version Summary PatchArch
    local isValid isNewVersion

    rm -f "${G_INSTALL_LIST}"
    touch "${G_INSTALL_LIST}"

    [ -n "${OSArch}" ] || OSArch="$(GetOSArch)"
    [ -n "${OSVer}" ] || OSVer="$(GetOSVersion)"
    [ -n "${OSMainline}" ] || OSMainline="$(GetOSMainlineVersion)"

    PackageManager="$(GetOSTyp)"
    PatchBaseDir="$(GetPatchFolder "${OSVer}" "${OSMainline}")"

    # Check patches list file
    if [ ! -f "${G_PATCHES_LIST}" ]; then
        # echo "Cannot found patches list file {$(basename "${G_PATCHES_LIST}")}"
        echo "无法找到补丁列表文件: {$(basename "${G_PATCHES_LIST}")}"
        exit 1
    fi
    # echo "Load list file {${G_PATCHES_LIST}}"
    echo "加载补丁列表: {${G_PATCHES_LIST}}"

    # echo "Generating Install List: ${G_INSTALL_LIST} ..."
    echo "正在生成安装列表: ${G_INSTALL_LIST} ..."
    grep -v "^#" "${G_PATCHES_LIST}" | grep "${PatchBaseDir}/" | while read -r Line; do
        PatchSum=${Line%% *}
        PatchFile=${Line##* }
        PatchSuffix=${PatchFile##*.}
        if [ "${PatchFile:0:1}" = "/" ]; then
            PatchFile=${PatchFile:1}
        fi
        ### Check patch file
        {
            ### Check file exists
            if [ ! -f "${PatchFile}" ]; then
                # echo "Cannot find patch file {${PatchFile}}!"
                echo "无法找到补丁文件: {${PatchFile}}!"
                exit 1
            fi
            ### Verify file Sha256Sum
            Sha256Sum=$(sha256sum "${PatchFile}" | awk '{print $1}')
            if [ "${PatchSum}" != "${Sha256Sum}" ]; then
                # echo "Bad Sha256Sum of {${PatchFile}}, break!"
                echo "文件 {${PatchFile}} 的 SHA256 校验和错误！"
                exit 1
            fi
            ### Check patch is valid for the current system
            # echo "DEBUG PackageManager: $PackageManager, Suffix: $PatchSuffix"
            if [ "${PackageManager}" != "${PatchSuffix}" ]; then
                # echo "Patch file {${PatchFile}} not match current package management system!"
                echo "补丁文件 {${PatchFile}} 不适用于当前系统的包管理框架！"
                exit 1
            fi
        }
        Name=''
        Version=''
        Summary=''
        PatchArch=''
        Depends=''
        isValid=false
        isBuiltInBroken=false
        isSameVersion=false
        isNewVersion=false
        # TODO: 增加文件类型检测，替换依据扩展名判断的方式。
        case "${PatchSuffix}" in
            deb)
                Name=$(dpkg-deb --info "${PatchFile}" | awk -F': ' '/^ Package/ {print $2}') || continue
                Version=$(dpkg-deb --info "${PatchFile}" | awk -F': ' '/^ Version/ {print $2}') || continue
                Summary=$(dpkg-deb --info "${PatchFile}" | awk -F': ' '/^ Description/ {print $2}') || continue
                PatchArch=$(dpkg-deb --info "${PatchFile}" | awk -F': ' '/^ Architecture/ {print $2}') || continue
                Depends=$(dpkg-deb --info "${PatchFile}" | awk -F': ' '/^ Depends/ {print $2}') || continue
                Depends=$(echo "${Depends}" | sed -e 's/ ([^)]*)//g' -e 's/ |/,/g' -e 's/, /,/g')

                ### Check if the patch is valid for the current architecture
                [ "${PatchArch}" = "all" ] && isValid=true
                [ "${PatchArch}" = "$(dpkg --print-architecture)" ] && isValid=true
                dpkg --print-foreign-architectures 2>/dev/null | grep -q "${PatchArch}" && isValid=true

                ### Check Patch Version and Built-in Version
                local BuiltInStatus BuiltInInfoLine BuiltInVersion
                if BuiltInStatus=$(dpkg-query --status "${Name}" 2> /dev/null | awk -F': ' '/^Status/ {print $2}') && [ -n "${BuiltInStatus}" ]; then
                    # echo "BuiltInCheck: ${Name} : ${BuiltInStatus}"
                    if echo "${BuiltInStatus}" | grep -q "install ok installed"; then
                        BuiltInVersion=$(dpkg-query --show "${Name}" 2> /dev/null | awk '{print $2}')
                        dpkg --compare-versions "${BuiltInVersion}" eq "${Version}" && isSameVersion=true
                        dpkg --compare-versions "${BuiltInVersion}" lt "${Version}" && isNewVersion=true
                    else
                        isBuiltInBroken=true
                    fi
                else
                    # echo "Package \"${Name}\" not installed, skip check version."
                    isNewVersion=true
                fi
                ;;
            rpm)
                Summary=$(rpm -qpi "${PatchFile}" | awk -F ': ' '/Summary/ {print $2}')
                PatchArch=$(rpm -qpi "${PatchFile}" | awk -F ': ' '/Architecture/ {print $2}')

                ## TODO: 需完善 RPM 校验
                isValid=true
                isNewVersion=true
                ;;
            *)
                # echo "Unsupported format of the patch!"
                echo "不支持的补丁格式!"
                continue
                ;;
        esac
        # echo "Matched ==> Valid: ${isValid}, BuiltInBroken: ${isBuiltInBroken}, New: ${isNewVersion}, Same: ${isSameVersion}, $(dirname "${PatchFile}") ${Name} - ${Version}"
        if [ ${isValid} = true ]; then
            if [ ${isBuiltInBroken} = true ] || [ ${isNewVersion} = true ]; then
                # echo "  Patch Valid: ${Name} - ${Version}"
                echo "  有效补丁: ${Name} - ${Version}"
                _IFS=${G_SPLIT_CHAR}
                echo "${Name}${_IFS}${Version}${_IFS}${Depends:=-}${_IFS}${OSArch}${_IFS}${OSVer}${_IFS}${PatchFile}${_IFS}${Summary}" | tee -a "${G_INSTALL_LIST}" > /dev/null
            fi
        fi
    done
    [ -f "${G_INSTALL_LIST}" ] || touch "${G_INSTALL_LIST}"
}

function Data_UpdateDepends() {
    if [ ! -f "${G_INSTALL_LIST}" ]; then
        # echo "Cannot find install list file ${G_INSTALL_LIST}."
        echo "无法找到安装列表文件: ${G_INSTALL_LIST}."
        exit 1
    fi

    local Name Version Depends OSArch OSVer File Summary
    local Result TargetLine CurrentLineNo ResultLineNo
    local PreviousPatch PreviousDepend
    local AllDone=false

    declare -a G_UPDATE_DEP_BLACK_LIST

    # echo "Updating dependencies ..."
    echo "正在更新依赖 ..."
    while [ "${AllDone}" == "false" ]; do
        AllDone=true
        CurrentLineNo=1
        while read -r Line; do
            Name=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $1}')
            Version=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $2}')
            Depends=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $3}')
            OSArch=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $4}')
            OSVer=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $5}')
            File=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $6}')
            Summary=$(echo "${Line}" | awk -F "${G_SPLIT_CHAR}" '{print $7}')

            if [ "${#G_UPDATE_DEP_BLACK_LIST[@]}" -gt 0 ]; then
                if echo "${G_UPDATE_DEP_BLACK_LIST[*]}" | grep -q "${Name}"; then
                    continue
                fi
            fi

            if [ "${Depends}" != "-" ]; then
                declare -a DependsArray
                read -r -a DependsArray <<< "$(echo ${Depends} | tr ',' ' ')"
                for Dep in "${DependsArray[@]}"; do
                    if Result=$(grep -n "^${Dep}${G_SPLIT_CHAR}" "${G_INSTALL_LIST}"); then
                        ResultLineNo=${Result%%:*}
                        if [ "${ResultLineNo}" -gt "${CurrentLineNo}" ]; then
                            ### Move the result line to the previous line
                            TargetLine=$(sed -n "${ResultLineNo}p" "${G_INSTALL_LIST}")
                            # echo "Move ${Dep}:${ResultLineNo} to ${CurrentLineNo}"
                            sed -i "${ResultLineNo}d;${CurrentLineNo}i\\${TargetLine}" "${G_INSTALL_LIST}" > /dev/null
                            if [ -n "${PreviousPatch}" ] && [ -n "${PreviousDepend}" ] && [ "${PreviousPatch}" == "${Dep}" ] && [ "${PreviousDepend}" == "${Name}" ]; then
                                # echo "  Warning: Circular dependency detected: ${Name} -> ${Dep} -> ${Name}"
                                echo "  警告: 检测到环形依赖: ${Name} -> ${Dep} -> ${Name}"
                                read -r -a G_UPDATE_DEP_BLACK_LIST <<< "${G_UPDATE_DEP_BLACK_LIST[*]} ${PreviousPatch} ${PreviousDepend}"
                            else
                                PreviousPatch="${Name}"
                                PreviousDepend="${Dep}"
                            fi
                            AllDone=false
                            break 2
                        fi
                    fi
                done
            fi
            CurrentLineNo=$((CurrentLineNo + 1))
        done < "${G_INSTALL_LIST}"
    done
}

function DEB_QueryInfo() {
    local Package=$1
    local Query=$2
    local ValidQueries=(
        Package
        Source
        Version
        Architecture
        Installed-Size
        Pre-Depends
        Depends
        Recommends
        Suggests
        Section
        Priority
        Multi-Arch
        Maintainer
        Description
    )
    declare -A InfoMap
    local Content KeyValue Key Value

    # echo "DEBUG: Package: ${Package}, Query: ${Query}"
    if echo "${ValidQueries[*]}" | grep -vq "${Query}"; then
        return 1
    fi

    Content=$(dpkg-deb --field "${Package}")
    for ValidQuery in "${ValidQueries[@]}"; do
        KeyValue=$(echo "${Content}" | grep "^${ValidQuery}") || continue
        Key="${KeyValue%: *}"
        Value="${KeyValue#*: }"
        [ -z "${Value}" ] && continue
        if [ "${Key}" = "Depends" ] || [ "${Key}" = "Pre-Depends" ] || [ "${Key}" = "Recommends" ] || [ "${Key}" = "Suggests" ]; then
            List="${Value}, "
            Value=''
            while [ "${List}" != "" ]; do
                Item=${List%%, *}
                Value="${Value:+${Value} }${Item%% *}"
                List=${List#*, }
            done
        fi
        # echo "DEBUG ${Key} = ${Value}"
        InfoMap["${Key}"]="${Value}"
    done

    echo "${InfoMap["${Query}"]}"
}

function DEB_InstallPatchs() {
    local Mode=$1
    local WorkDir=$2
    local UserID=$3
    local PatchIndex=1
    local PatchCount=0
    local ReturnCode=0
    local OSType OSVer OSMainline PatchBaseDir ActionsDir
    local PatchFiles PatchNames
    local PatchFile PatchSuffix

    SetInstallerMode "${Mode}"
    pushd "${WorkDir}" > /dev/null || return $?

    OSType="$(GetOSTyp)"
    OSVer="$(GetOSVersion)"
    OSMainline="$(GetOSMainlineVersion)"
    PatchBaseDir="$(GetPatchFolder "${OSVer}" "${OSMainline}")"
    ActionsDir="$(find . -type d -name "${G_ACTIONS_D}" | grep "${PatchBaseDir}/${G_ACTIONS_D}$" | head -n 1)"
    ActionsPre="${ActionsDir}/${G_ACTIONS_FILE_PRE}"
    ActionsPost="${ActionsDir}/${G_ACTIONS_FILE_POST}"

    if [ ! -f "${G_INSTALL_LIST}" ]; then
        # echo "Cannot find patches intall list file {${G_INSTALL_LIST}}."
        echo "无法找到补丁安装列表文件: {${G_INSTALL_LIST}}."
        return 1
    fi
    PatchCount=$(grep -cv "^#" "${G_INSTALL_LIST}")

    rm -f "${G_INSTALL_LOG}"
    touch "${G_INSTALL_LOG}"

    ### ${Name} | ${Version} | ${Depends} | ${OSArch} | ${OSVer} | ${PatchFile} | ${Summary}
    read -r -a PatchNames <<< "$(grep -v "^#" "${G_INSTALL_LIST}" | awk -F "${G_SPLIT_CHAR}" '{print $1}' | tr '\n' ' ')"
    read -r -a PatchFiles <<< "$(grep -v "^#" "${G_INSTALL_LIST}" | awk -F "${G_SPLIT_CHAR}" '{print $6}' | tr '\n' ' ')"

    # Call Pre-Actions
    if [ ${#PatchFiles[*]} -gt 0 ]; then
        echo "# 正在调用安装前脚本 ..."
        Call_Actions "Pre" "${ActionsPre}" "${UserID}" >> "${G_INSTALL_LOG}" 2>&1
    fi

    # Unpack patches
    if [ ${#PatchFiles[*]} -gt 0 ]; then
        echo "Unpack Patches ..." >> "${G_INSTALL_LOG}"
        echo "====================================================================================" >> "${G_INSTALL_LOG}"
        for PatchFile in "${PatchFiles[@]}"; do
            # echo "DEBUG: Unpack Patch: ${PatchFile}" >> "${G_INSTALL_LOG}"
            PatchSuffix=${PatchFile##*.}
            [ -f "${PatchFile}" ] || continue;
            [ "${OSType}" = "${PatchSuffix}" ] || continue;
            case "${PatchSuffix}" in
                deb)
                    if [ "${G_INSTALLER_MODE}" = "GUI" ]; then
                        echo "# 正在释放补丁 $(dpkg-deb --show "${PatchFile}" | awk '{print $1}') ..."
                        echo ""
                    else
                        # echo "Unpacking $(dpkg-deb --show "${PatchFile}" | awk '{print $1}') ..."
                        echo "正在释放补丁 $(dpkg-deb --show "${PatchFile}" | awk '{print $1}') ..."
                    fi
                    unset DEBIAN_HAS_FRONTEND
                    UCF_FORCE_CONFFNEW=YES DEBIAN_FRONTEND=noninteractive dpkg --no-debsig --auto-deconfigure --unpack "${PatchFile}" >> "${G_INSTALL_LOG}" 2>&1
                    ((ReturnCode = ReturnCode + $?))
                    ;;
                rpm)
                    if ! rpm -ivh "${PatchFile}" >> "${G_INSTALL_LOG}" 2>&1; then
                        # echo "Invalid Patch for this OS, Skip!" >> "${G_INSTALL_LOG}"
                        echo "不适用于当前系统的补丁！" >> "${G_INSTALL_LOG}"
                        continue
                    fi
                    ;;
                *)
                    # echo "Unsupported format of the patch!" >> "${G_INSTALL_LOG}"
                    echo "不支持的补丁格式！" >> "${G_INSTALL_LOG}"
                    continue
                    ;;
            esac
            if [ "${G_INSTALLER_MODE}" = "GUI" ]; then
                echo "$((PatchIndex * 90 / PatchCount))"
            fi
            PatchIndex=$((PatchIndex + 1))
        done
        echo "====================================================================================" >> "${G_INSTALL_LOG}"
        echo "Unpack-Done" >> "${G_INSTALL_LOG}"
    fi

    # Configure All Unpacked Patches
    if [ ${#PatchNames[*]} -gt 0 ]; then
        if [ "${ReturnCode}" -eq 0 ]; then
            echo "Configure Patches ..." >> "${G_INSTALL_LOG}"
            echo "====================================================================================" >> "${G_INSTALL_LOG}"
            case "${OSType}" in
                deb)
                    if [ "${G_INSTALLER_MODE}" = "GUI" ]; then
                        echo "# 正在配置补丁 ..."
                        echo ""
                    else
                        # echo "Configuring ..."
                        echo "正在配置补丁 ..."
                    fi
                    unset DEBIAN_HAS_FRONTEND
                    eval UCF_FORCE_CONFFNEW=YES  DEBIAN_FRONTEND=noninteractive dpkg --configure "${PatchNames[*]}" >> "${G_INSTALL_LOG}" 2>&1
                    ((ReturnCode = ReturnCode + $?))
                    ;;
                rpm)
                    ;;
                *)
                    ;;
            esac
            echo "====================================================================================" >> "${G_INSTALL_LOG}"
            echo "Configure-Done" >> "${G_INSTALL_LOG}"
        fi
    fi

    # Call Post-Actions
    if [ ${#PatchFiles[*]} -gt 0 ] && [ "${ReturnCode}" -eq 0 ]; then
        echo "# 正在调用安装后脚本 ..."
        Call_Actions "Post" "${ActionsPost}" "${UserID}" >> "${G_INSTALL_LOG}" 2>&1
    fi

    # Change owner of install-log
    [ -n "${UserID}" ] && [ -f "${G_INSTALL_LOG}" ] && chown "$(id -u "${UserID}")":"$(id -g "${UserID}")" "${G_INSTALL_LOG}"

    # Create install status flag
    if [ "${ReturnCode}" -eq 0 ]; then
        touch "DEB_InstallPatchs.Success"
    else
        touch "DEB_InstallPatchs.Failed"
    fi

    popd > /dev/null || return $?

    if [ "${G_INSTALLER_MODE}" = "GUI" ]; then
        echo 100
    fi
}

function DEB_Installation() {
    local Mode=$1
    local Title=$2
    local Contents=$3
    local Workdir=$4
    local User=$5
    local UserID

    if [ -z "${Workdir}" ]; then
        Workdir=${EXEC_DIR}
    fi
    if [ -z "${User}" ]; then
        if [ -n "${SUDO_USER}" ]; then
            User=${SUDO_USER}
        else
            User=${USER}
        fi
    fi
    UserID=$(id -u "${User}")

    PatchesCount=$(grep -c -v "^#" "${G_INSTALL_LIST}" 2> /dev/null)
    # echo "Patches Count: ${PatchesCount}"
    echo "补丁数量: ${PatchesCount}"
    if [ "${PatchesCount}" -le 0 ]; then
        GUI_ShowInfo "${G_INSTALLER_MODE}" info "安装结束" "没有补丁需要安装。"
        return
    fi

    SetInstallerMode "${Mode}"
    if [ "${G_INSTALLER_MODE}" = "GUI" ] && HasZenity; then
        GUI_Caller "${Mode}" "${EXEC_DIR}/FUNCTIONS" "DEB_InstallPatchs GUI \"${Workdir}\" \"${UserID}\"" | GUI_ShowProgress "${G_INSTALLER_MODE}" "${Title}" "${Contents}"
    else
        GUI_Caller "${Mode}" "${EXEC_DIR}/FUNCTIONS" "DEB_InstallPatchs CLI \"${Workdir}\" \"${UserID}\""
    fi

    if [ -f "DEB_InstallPatchs.Success" ]; then
        GUI_ShowInfo "${G_INSTALLER_MODE}" info "安装完成" "安装完成！请重启系统，以使补丁生效。"
        # Reboot System
    else
        GUI_ShowInfo "${G_INSTALLER_MODE}" error "安装失败" "安装失败！请检查安装日志。"
        GUI_ShowInfo "${G_INSTALLER_MODE}" text-info "安装日志" "${G_INSTALL_LOG}"
    fi
}
